/***********************************************************************
 * BinTrotter : BitTorrent library/client for the Android operating system.
 *  
 * Copyright (C) 2009, Christopher David Stones (chris.stones@gmail.com)
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 ***********************************************************************/

package org.bintrotter.afiles;

import java.io.File;
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.bintrotter.Main;
import org.bintrotter.contentmanager.ContentManager;
import org.bintrotter.metafile.Metafile;

public class Files {

	public final Metafile 						metafile;
	private final String 						fileSystemRoot;
	private java.io.File 						filesRoot;
	private List<org.bintrotter.afiles.File>	fileList;
	
	private ContentManager havePieces;
	
	
	public Files(Metafile mf, String fsr) throws Exception {
		
		this.metafile = mf;
		this.fileSystemRoot = fsr;

		filesRoot = new File(fileSystemRoot);

		if (filesRoot.exists() == false)
			filesRoot.mkdirs();

		fileList = new LinkedList<org.bintrotter.afiles.File>();

		if (metafile.singleFileInfo != null) {

			fileList.add(new org.bintrotter.afiles.File(new File(filesRoot,
					metafile.singleFileInfo.name),
					0,
					metafile.singleFileInfo.length));

		} else {

			filesRoot = new File(filesRoot, metafile.multiFileInfo.name);

			/*** create down load root directory ***/
			if (filesRoot.exists() == false)
				filesRoot.mkdirs();

			long firstByte = 0;

			/*** iterate through files ***/
			for (Iterator<Metafile.MultiFileInfo.Files> it = metafile.multiFileInfo.files
					.iterator(); it.hasNext();) {

				/*** create parent directory structure for this file ***/

				File f = null;

				Metafile.MultiFileInfo.Files files = it.next();

				List<String> path = files.path;

				for (int i = 0; i < path.size(); ++i) {

					if (f == null)
						f = new File(filesRoot, path.get(i));
					else {
						if (i == (path.size() - 1))
							if (f.exists() == false) {
								f.mkdirs();
							}

						f = new File(f, path.get(i));
					}
				}

				fileList.add(new org.bintrotter.afiles.File(f, firstByte, files.length));

				firstByte += files.length;
			}
		}
		
		hashAll();
	}
	
	public boolean hashAll() throws Exception {
		
		havePieces = new ContentManager(16); // FIXME: 2^x==metafile.pieces();
		int max = metafile.pieces();
		int have = 0;
		for(int i=0; i<max; i++)
			if(Hasher.SHA1(i, this)) {
				havePieces.set((int)i);
				++have;
			} else
				havePieces.unset((int)i);
		
		Main.log.info("have " + have + " of " + max + " pieces");
		
		return have >= max;
	}
	
	public ContentManager getHavePieceMask() {
		
		return havePieces;
	}
	
	public void close() throws IOException {

		for (org.bintrotter.afiles.File fc : fileList)
			fc.close();
	}
	
	public MappedByteBuffer[] mmap(long streamAddress, long len) throws IOException {
		
		ArrayList<MappedByteBuffer> list = new ArrayList<MappedByteBuffer>();
		
		while(len>0) {
		
			boolean mappedsomthing = false;
			
			for (Iterator<org.bintrotter.afiles.File> it = fileList.iterator(); it.hasNext() && (len > 0);) {
	
				org.bintrotter.afiles.File fc = it.next();
	
				if((fc.streamAddress <= streamAddress) && (fc.streamAddress + ((int)fc.size) > streamAddress)) {
						
					long toMap = len;
					if((streamAddress + toMap) > (fc.streamAddress + fc.size))
						toMap = fc.size - (streamAddress - fc.streamAddress);
					
					assert(toMap>0);
					
					list.add( fc.mmap(streamAddress-fc.streamAddress, toMap) );
					
					streamAddress	+= toMap;
					len				-= toMap;
					
					mappedsomthing = true;

					if(len<0)
						throw new IOException("oops");
					
					break;
				}
			}
			
			if(mappedsomthing==false)
				break;
		}
		
		if(list.size()==0)
			return null;
		
		MappedByteBuffer[] mbb = new MappedByteBuffer[ list.size() ];
		for(int i=0;i<list.size(); ++i)
			mbb[i] = list.get(i);
		
		return mbb;
	}
	
	public MappedByteBuffer[] mmap(int piece) throws IOException {
			
		long len = metafile.pieceLength();
		long streamAddress = piece * len;
		
		return mmap(streamAddress,len);
	}
	
	public MappedByteBuffer[] mmap(int piece, int offset, int _len) throws IOException
	{
		
		long len = _len;
		long streamAddress = metafile.pieceLength() * piece + offset;
		
		return mmap(streamAddress, len);
	}

}



